package com.fary.cloud.netflix.eureka.server;

import com.fary.cloud.netflix.eureka.server.event.EurekaInstanceCanceledEvent;
import com.fary.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent;
import com.fary.cloud.netflix.eureka.server.event.EurekaInstanceRenewedEvent;
import com.fary.context.ApplicationContext;
import com.fary.context.ApplicationContextAware;
import com.fary.context.ApplicationEvent;
import com.fary.core.SpringException;
import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.EurekaClientConfig;
import com.netflix.discovery.shared.Application;
import com.netflix.eureka.EurekaServerConfig;
import com.netflix.eureka.lease.Lease;
import com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl;
import com.netflix.eureka.resources.ServerCodecs;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.List;

/**
 * InstanceRegistry该类也是springcloud对于eureka的PeerAwareInstanceRegistry类的包装，
 * 对PeerAwareInstanceRegistry提供对各PeerNode对等节点的管理的功能
 * (任何客户端发送到集群中的任意一台eureka中动作，都需要向该eureka服务保管的对等节点列表中任意一个函数都要进行相同的处理，
 * 其中包含注册register方法，删除cancel方法，续约等功能)的增强，
 * 并且开启各种不同的定时任务(例如驱逐失效服务节点，监控当前服务与各客户端连连接情况，
 * 失败率是否超过了一定阈值:默认是15min(renewal-threshold-update-interval-ms)内超过85%(通过renewal-percent-threshold设置)的，
 * 是否需要开启自我保护机制)，本质是对操作进行拦截并推送Event事件，在应用中我们可以监控不同事件做些不同处理。
 * 其它都是使用PeerAwareInstanceRegistry的原装方法。
 */
public class InstanceRegistry extends PeerAwareInstanceRegistryImpl implements ApplicationContextAware {

    private static final Log log = LogFactory.getLog(InstanceRegistry.class);

    private ApplicationContext ctxt;

    private int defaultOpenForTrafficCount;

    public InstanceRegistry(EurekaServerConfig serverConfig,
                            EurekaClientConfig clientConfig, ServerCodecs serverCodecs,
                            EurekaClient eurekaClient, int expectedNumberOfClientsSendingRenews,
                            int defaultOpenForTrafficCount) {
        super(serverConfig, clientConfig, serverCodecs, eurekaClient);

        this.expectedNumberOfClientsSendingRenews = expectedNumberOfClientsSendingRenews;
        this.defaultOpenForTrafficCount = defaultOpenForTrafficCount;
    }

    @Override
    public void setApplicationContext(ApplicationContext context) throws SpringException {
        this.ctxt = context;
    }

    /**
     * If
     * {@link PeerAwareInstanceRegistryImpl#openForTraffic(ApplicationInfoManager, int)}
     * is called with a zero argument, it means that leases are not automatically
     * cancelled if the instance hasn't sent any renewals recently. This happens for a
     * standalone server. It seems like a bad default, so we set it to the smallest
     * non-zero value we can, so that any instances that subsequently register can bump up
     * the threshold.
     */
    @Override
    public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
        super.openForTraffic(applicationInfoManager, count == 0 ? this.defaultOpenForTrafficCount : count);
    }

    @Override
    public void register(InstanceInfo info, int leaseDuration, boolean isReplication) {
        handleRegistration(info, leaseDuration, isReplication);
        super.register(info, leaseDuration, isReplication);
    }

    @Override
    public void register(final InstanceInfo info, final boolean isReplication) {
        handleRegistration(info, resolveInstanceLeaseDuration(info), isReplication);
        super.register(info, isReplication);
    }

    @Override
    public boolean cancel(String appName, String serverId, boolean isReplication) {
        handleCancelation(appName, serverId, isReplication);
        return super.cancel(appName, serverId, isReplication);
    }

    @Override
    public boolean renew(final String appName, final String serverId, boolean isReplication) {
        log("renew " + appName + " serverId " + serverId + ", isReplication {}" + isReplication);
        List<Application> applications = getSortedApplications();
        for (Application input : applications) {
            if (input.getName().equals(appName)) {
                InstanceInfo instance = null;
                for (InstanceInfo info : input.getInstances()) {
                    if (info.getId().equals(serverId)) {
                        instance = info;
                        break;
                    }
                }
                publishEvent(new EurekaInstanceRenewedEvent(this, appName, serverId, instance, isReplication));
                break;
            }
        }
        return super.renew(appName, serverId, isReplication);
    }

    @Override
    protected boolean internalCancel(String appName, String id, boolean isReplication) {
        handleCancelation(appName, id, isReplication);
        return super.internalCancel(appName, id, isReplication);
    }

    private void handleCancelation(String appName, String id, boolean isReplication) {
        log("cancel " + appName + ", serverId " + id + ", isReplication "
                + isReplication);
        publishEvent(new EurekaInstanceCanceledEvent(this, appName, id, isReplication));
    }

    private void handleRegistration(InstanceInfo info, int leaseDuration,
                                    boolean isReplication) {
        log("register " + info.getAppName() + ", vip " + info.getVIPAddress()
                + ", leaseDuration " + leaseDuration + ", isReplication "
                + isReplication);
        publishEvent(new EurekaInstanceRegisteredEvent(this, info, leaseDuration,
                isReplication));
    }

    private void log(String message) {
        if (log.isDebugEnabled()) {
            log.debug(message);
        }
    }

    private void publishEvent(ApplicationEvent applicationEvent) {
        this.ctxt.publishEvent(applicationEvent);
    }

    private int resolveInstanceLeaseDuration(final InstanceInfo info) {
        int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
        if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
            leaseDuration = info.getLeaseInfo().getDurationInSecs();
        }
        return leaseDuration;
    }

}